/*
 * Copyright (c) 2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include "file_share.h"
#include <dirent.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include "bundle_mgr_interface.h"
#include "bundlemgr/bundle_mgr_proxy.h"
#include "file_share_log.h"
#include "ipc_skeleton.h"
#include "iservice_registry.h"
#include "status_receiver_host.h"
#include "system_ability_definition.h"
#include "uri.h"


namespace OHOS {
namespace AppFileService {
namespace ModuleFileShare {
using namespace OHOS::AppExecFwk;

static string GetBundleName(Security::AccessToken::AccessTokenID tokenID)
{
    Security::AccessToken::HapTokenInfo hapInfo;
    int result = Security::AccessToken::AccessTokenKit::GetHapTokenInfo(tokenID, hapInfo);
    if (result != 0) {
        LOGI("FileShare::GetBundleName, failed to get hap token info %{public}d", result);
    }
    LOGI("FileShare::GetBundleName, bundle Name is %{public}s", hapInfo.bundleName.c_str());
    return hapInfo.bundleName.c_str();
}

static sptr<IBundleMgr> GetBundleMgrProxy()
{
    sptr<ISystemAbilityManager> systemAbilityManager =
        SystemAbilityManagerClient::GetInstance().GetSystemAbilityManager();
    if (!systemAbilityManager) {
        LOGE("fail to get system ability mgr.");
        return nullptr;
    }

    sptr<IRemoteObject> remoteObject = systemAbilityManager->GetSystemAbility(BUNDLE_MGR_SERVICE_SYS_ABILITY_ID);
    if (!remoteObject) {
        LOGE("fail to get bundle manager proxy.");
        return nullptr;
    }

    LOGI("FileShare::GetBundleMgrProxy, get bundle manager proxy success.");
    return iface_cast<IBundleMgr>(remoteObject);
}

static string GetBundleNameSelf()
{
    int uid = -1;
    uid = IPCSkeleton::GetCallingUid();
    
    sptr<IBundleMgr> bundleMgrProxy = GetBundleMgrProxy();
    if (!bundleMgrProxy) {
        LOGE("FileShare::GetBundleNameSelf, bundle mgr proxy is nullptr.");
        return nullptr;
    }

     string bundleName;
    if (!bundleMgrProxy->GetBundleNameForUid(uid, bundleName)) {
        LOGE("FileShare::GetBundleNameSelf, bundleName get fail. uid is %{public}d", uid);
        return nullptr;
    }

    LOGI("FileShare::GetBundleNameSelf, uid is %{public}d, bundleName is %{public}s", uid, bundleName.c_str());
    return bundleName;
}

static string GetCurrentUid(Security::AccessToken::AccessTokenID tokenID)
{
    Security::AccessToken::HapTokenInfo hapInfo;
    int result = Security::AccessToken::AccessTokenKit::GetHapTokenInfo(tokenID, hapInfo);
    if (result != 0) {
        LOGI("FileShare::GetCurrentUid, failed to get hap token info %{public}d", result);
    }
    return to_string(hapInfo.userID);
}

static string GetRealPath(const string &uri, Security::AccessToken::AccessTokenID tokenID)
{
    Uri uriTemp(uri);
    string scheme = uriTemp.GetScheme();
    string::size_type posFile = scheme.find(SCHEME);
    if(posFile == string::npos){
        return "";
    }

    string path = uriTemp.GetPath();
    string::size_type posPath = path.find(PATH_SYMBOLS);
    if (posPath == string::npos) {
        return "";
    }
    size_t num = sizeof(sandBoxPath) / sizeof(sandBoxPath[0]);
    string fragmentPath;
    string srcPathTemp;
    for (size_t i = 0; i < num; i++)
    {
        if(path.length() >= sandBoxPath[i].length()){
           string  sandBoxPathTemp = path.substr(0, sandBoxPath[i].length());
           if(sandBoxPathTemp == sandBoxPath[i]) {
              srcPathTemp = srcPath[i];
              fragmentPath = path.substr(sandBoxPath[i].length());
            }
        }
    }
    
    if (srcPathTemp.find(CURRENTUSERID) != string::npos) {
        srcPathTemp = srcPathTemp.replace(srcPathTemp.find(CURRENTUSERID), CURRENTUSERID.length(), 
        GetCurrentUid(tokenID));
    }

    if (srcPathTemp.find(PACKAGENAME) != string::npos) {
        srcPathTemp = srcPathTemp.replace(srcPathTemp.find(PACKAGENAME), PACKAGENAME.length(), 
        GetBundleName(tokenID));
    }
    LOGI("FileShare::GetRealPath, srcPathTemp1 is %{public}s", srcPathTemp.c_str());
    srcPathTemp += fragmentPath;
    return srcPathTemp;
}

static bool MakeDirectory(const string &path, int flag)
{
    string::size_type index = 0;
    string subPath;
    do {
        index = path.find('/', index + 1);
        if (index == string::npos) {
            subPath = path;
        } else {
            subPath = path.substr(0, index);
        }

        if (access(subPath.c_str(), 0) != 0) {
            if (mkdir(subPath.c_str(), flag) != 0) {
                return false;
            }
        }
    } while (index != string::npos);

    LOGE("FileShare::MakeDirectory, make dir is %{public}s", subPath.c_str());
    return true;
}

static bool IsExistDir(const std::string &path)
{
    if (path.empty()) {
        return false;
    }

    struct stat buf = {};
    if (stat(path.c_str(), &buf) != 0) {
        return false;
    }
    return S_ISDIR(buf.st_mode);
}

static bool IsExistFile(const std::string &path)
{
    if (path.empty()) {
        return false;
    }

    struct stat buf = {};
    if (stat(path.c_str(), &buf) != 0) {
        return false;
    }
    return S_ISREG(buf.st_mode);
}

static string GetLinksPath(const string &uri, Security::AccessToken::AccessTokenID tokenID, int flag)
{
    string sharePath;
    string realPath = GetRealPath(uri, tokenID);
    string uid = GetCurrentUid(tokenID);
    string bundleName = GetBundleName(tokenID);
    string bundleNameSelf = GetBundleNameSelf();
    string flagStr = to_string(flag);

    if(IsExistFile(realPath))
    {
        sharePath = REAL_DATA_PATH + "/" + uid + "/" + "share" + "/" + bundleNameSelf + "/" + 
        bundleName + "/" + flagStr + realPath;
    }
    if(IsExistDir(realPath))
    {
        sharePath = REAL_DATA_PATH + "/" + uid + "/" + "share" + "/" + bundleNameSelf + "/" + 
        bundleName + "/" + flagStr + realPath;

    }

    LOGE("FileShare::GetLinksPath, share Path is %{public}s", sharePath.c_str());
    return sharePath;
}

string IncludeTrailingPathDelimiter(const std::string& path)
{
    if (path.rfind("/") != path.size() - 1) {
        return path + "/";
    }

    return path;
}

bool RemoveFile(const string& fileName)
{
    if (access(fileName.c_str(), F_OK) == 0) {
        return remove(fileName.c_str()) == 0;
    }

    return true;
}

static bool RemoveDirectory(const string& path)
{
    string subPath;
    bool ret = true;
    DIR *dir = opendir(path.c_str());
    if (dir == nullptr) {
        return false;
    }

    while (true) {
        struct dirent *ptr = readdir(dir);
        if (ptr == nullptr) {
            break;
        }

        if (strcmp(ptr->d_name, ".") == 0 || strcmp(ptr->d_name, "..") == 0) {
            continue;
        }
		subPath = IncludeTrailingPathDelimiter(path) + string(ptr->d_name);

        subPath = path + string(ptr->d_name);
        if (ptr->d_type == DT_DIR) {
            ret = RemoveDirectory(subPath);
        } else {
            if (access(subPath.c_str(), F_OK) == 0) {
                if (remove(subPath.c_str()) != 0) {
                    closedir(dir);
                    return false;
                }
            }
        }
    }
    closedir(dir);

    return ret;
}

bool DeleteDir(const std::string &path)
{
    if (IsExistFile(path)) {
        return RemoveFile(path);
    }
    if (IsExistDir(path)) {
        return RemoveDirectory(path);
    }
    return true;
}

int FileShare::CreateShareFile(const string &uri, Security::AccessToken::AccessTokenID tokenID, int flag)
{
    string dstPath;
    string realPath = GetRealPath(uri, tokenID);
    LOGE("FileShare::CreateShareFile, real Path is %{public}s", realPath.c_str());
    if(IsExistFile(realPath))
    {
        LOGI("FileShare::CreateShareFile, file");
        if (access(realPath.c_str(), 0) == 0) {
            dstPath = GetLinksPath(uri, tokenID, flag);
            string fileName = dstPath.substr(dstPath.find_last_of("/") + 1);
            string makePath = dstPath.erase(dstPath.find_last_of("/"));
            if(!MakeDirectory(makePath.c_str(), flag)){
                LOGE("FileShare::CreateShareFile, make dir failed with %{public}d", errno);
                return errno;
            }
            if(link(realPath.c_str(), (makePath + "/" + fileName).c_str()) != 0){
                LOGI("FileShare::CreateShareFile, symlink failed with %{public}d", errno);
                return errno;
            }
        }
        else{
                return errno;
                LOGI("FileShare::CreateShareFile, dir Path not Exits");
        }
    }
    if(IsExistDir(realPath))
    {
        LOGI("FileShare::CreateShareFile, dir");
         if (access(realPath.c_str(), 0) == 0) {
            dstPath = GetLinksPath(uri, tokenID, flag);
            if(!MakeDirectory(dstPath.c_str(), flag)){
                LOGE("FileShare::CreateShareFile, make dir failed with %{public}d", errno);
                return errno;
            }
            if(link(realPath.c_str(), dstPath.c_str())!= 0){
                LOGI("FileShare::CreateShareFile, symlink failed with %{public}d", errno);
                return errno;
            }
         }
         else{
                LOGI("FileShare::CreateShareFile, symlink failed with %{public}d", errno);
                return errno;
         }

    }
    LOGI("FileShare::CreateShareFile, Create Share File Success");
    return 0;
}

int FileShare::DeleteShareFile(const string &uri, Security::AccessToken::AccessTokenID tokenID, int flag)
{
    string sharePath = REAL_DATA_PATH + "/" + "100" + "/" + "share";
    if(!DeleteDir(sharePath.c_str()))
    {
        LOGE("FileShare::DeleteShareFile, delete dir failed with %{public}d", errno);
        return errno;
    }
    LOGI("FileShare::DeleteShareFile, delete Share File Success");
    return 0;
}
} // namespace ModuleFileShare
} // namespace AppFileService
} // namespace OHOS
